#!/usr/bin/python
# encoding: utf-8

"""
@file: ave_value_ineqs.py
@time: 16/9/18 下午3:34
"""
from functools import reduce

# 均值不等式问题
from mathsolver.functions.base import *
from mathsolver.functions.budengshi import common_opers as co
from sympy import S, expand, fraction, simplify, cancel, degree, sqrt, solve, logcombine, log, collect, Pow, solveset
from sympy.core.numbers import NaN
from sympy.sets.fancysets import Reals


# 1.和定积最大
class AveValueIneq001(BaseFunction):
    """

    """
    @staticmethod
    def sim_ineq(ineq):
        """
        化简不等式
        :param ineq:
        :return:
        """
        ll, o, r = ineq.sympify()
        c = co.symbol_coeff(ll)
        ll = ll / c
        if c < 0:
            o = o.replace('>', '<') if o.find('>') >= 0 else o.replace('<', '>')
        r = r / c
        return BaseIneq([ll, o, r])

    @staticmethod
    def asp_ineq(sum_eq):
        """
        和定积不等式
        :param sum_eq: 和表达式
        :return: 得出的不等式列表
        """
        l, r = sum_eq.sympify()
        symbs = l.free_symbols
        symbs_intervals = map(lambda s: (s, Interval(0, S.Infinity)), symbs)
        d = degree(l)
        t1, t2 = map(lambda t: t[0] * t[1], l.as_coefficients_dict().items())
        ineq_l = t1 * t2
        ineq_r = r
        if d == 1:
            ineq_l = co.sqrtsimp(sqrt(ineq_l ** 2), symbs_intervals)
            return BaseIneq([4 * ineq_l, '<=', ineq_r ** 2])
        else:
            ineq_l = co.sqrtsimp(sqrt(ineq_l), symbs_intervals)
            return BaseIneq([2 * ineq_l, '<=', ineq_r])

    def solver(self, *args):
        conds = args[0]  # 已知条件
        eq = args[1]  # 满足的等式
        eq_left, eq_right = eq.sympify()
        target_e = args[2].sympify()  # 目标表达式
        log_target_e = logcombine(target_e, force=True)
        target_fuc = lambda f: f
        if isinstance(log_target_e, log):
            target_fuc = log
            target_e = logcombine(target_e, force=True)
        target_e_coeff = target_e.as_coefficients_dict().values()[0]
        if isinstance(conds, BaseIneqs):
            self.steps.append(['因为:', conds.printing()])
        t1, t2 = map(lambda t: t[0] * t[1], eq_left.as_coefficients_dict().items())
        self.steps.append(['所以 %s = %s >= %s' % (new_latex(eq_right), new_latex(eq_left), new_latex(2 * sqrt(t1 * t2))), ''])
        target_ineq = AveValueIneq001.sim_ineq(AveValueIneq001.asp_ineq(eq))
        self.steps.append(['即:' + target_ineq.printing(), ''])
        self.steps.append(['当且仅当%s = %s' % (new_latex(t1), new_latex(t2)), '取得等号。'])
        max_value = target_fuc(target_e_coeff * target_ineq.sympify()[-1])
        self.steps.append(
            ['所以%s 的最大值为: %s' % (new_latex(target_e), new_latex(max_value)), ''])
        self.output.append(BaseValue(max_value))
        self.label.add('和定积最大')
        return self


# 2.积定和最小
class JiDingHeZuiXiao(BaseFunction):
    """
    已知x>0,y>0,xy=2,则x+2y的最小值是().
    """
    def solver(self, *args):
        self.label.add('积定和最小')
        cond_eq = args[0]  # 满足的条件
        tmp_cond_eq = co.log_ded_eq(cond_eq)
        if tmp_cond_eq != cond_eq:
            self.steps.append([r'\because ', cond_eq.printing()])
            tmp_cond_eq_l, tmp_cond_eq_r = tmp_cond_eq.sympify()
            tmp_cond_f = tmp_cond_eq_l - tmp_cond_eq_r
            tmp_cond_mo = fraction(cancel(tmp_cond_f))[0]
            _, tmp_const = co.symoms_and_const(tmp_cond_mo)
            new_right = tmp_cond_mo - tmp_const
            tmp_cond_eq = BaseEq([new_right, -tmp_const])
            self.steps.append([r'\therefore ', tmp_cond_eq.printing()])
            cond_eq = tmp_cond_eq
        eq_left, eq_right = cond_eq.sympify()
        target_e = args[1].sympify()  # 目标表达式
        if str(target_e).find('Abs') >= 0:
            target_e = target_e.args[0]
        target_e_symons, target_const = co.symoms_and_const(target_e)
        symons, const = co.symoms_and_const(target_e)
        ineq_right = 2 * sqrt(reduce(lambda a, b: a * b, symons))
        target_ineq = BaseIneq([target_e, '>=', ineq_right + const])
        self.steps.append([r'\because ', cond_eq.printing()])
        ineq_right_value = ineq_right.subs(eq_left, eq_right)
        if not ineq_right_value.is_real:
            raise Exception('Type Match Error')
        self.steps.append([r'\therefore ' + target_ineq.printing() + ' = ', new_latex(ineq_right_value + target_const)])
        if len(target_e_symons) == 1:  # [2**x*2**y]
            tmp_f = simplify(target_e_symons[0])
            if isinstance(tmp_f, Pow):
                f2 = tmp_f.args[1]
                mons, _ = co.symoms_and_const(f2)
                t1, t2 = mons
        else:
            t1, t2 = target_e_symons
        eq_print = BaseEq([t1, t2]).printing()
        self.steps.append(['当且仅当:' + eq_print,
                           ' = ' + new_latex(ineq_right_value / 2) + ', 即 x 为: %s 时取等号' % new_latex(
                               solve(t1 - ineq_right_value / 2))])
        self.steps.append(['故答案为:' + str(ineq_right_value + target_const), ''])
        self.output.append(BaseNumber(ineq_right_value + target_const))
        return self


# 3.巧用数字求最值
class OpValueWithNum(BaseFunction):
    @staticmethod
    def deduct(e, one_repl):
        """
        自动推理
        :param e: 表达式
        :param one_repl: 等于1的表达式
        :return:
        """

        def ended(t_e):  # 判断式子是否可以总结
            t_e = expand(t_e)
            ts = t_e.as_coefficients_dict().items()
            symb_terms = filter(lambda term: term[0] != 1, ts)
            symb_terms = map(lambda t: t[0] * t[1], symb_terms)
            st_mul = reduce(lambda a, b: a * b, symb_terms)
            if st_mul.is_real:
                return True
            else:
                return False

        steps = []
        tmp_e = e
        step_nums = 0
        while True:
            if step_nums > 20:
                raise Exception('try error!')
            steps.append(expand(tmp_e))
            if ended(tmp_e):
                break
            terms = tmp_e.as_coefficients_dict().items()
            tmp_e = sympify('0')
            for t, c in terms:
                tmp_e += t * c * one_repl
            steps.append(tmp_e)
            step_nums += 1
        return steps

    @staticmethod
    def hold_ineq(e):
        e = expand(sympify(e))
        ts = e.as_coefficients_dict().items()
        cont, syb_terms = sympify('0'), []
        for t, c in ts:
            if t == 1:
                cont += t * c
            else:
                syb_terms.append(t * c)
        t_mul = reduce(lambda a, b: a * b, syb_terms)
        return BaseIneq([e, '>=', cont + 2 * sqrt(t_mul)])

    def solver(self, *args):
        self.label.add('巧用数字求最值')
        cond = args[0]
        eq = args[1]
        eq = co.right_const_eq(eq)
        tmp_cond_eq = co.log_ded_eq(eq)
        if tmp_cond_eq != eq:
            self.steps.append(['\\because ', eq.printing()])
            self.steps.append(['\\therefore ', tmp_cond_eq.printing()])
            eq = tmp_cond_eq
        l, r = eq.sympify()
        l, r = l / r, r / r
        symbs = list(l.free_symbols)
        t_e = args[2].sympify()  # 目标表达式
        steps = OpValueWithNum.deduct(t_e, l)
        if isinstance(cond, BaseIneqs):
            self.steps.append(['由题可知: ', cond.printing()])
        self.steps.append(['\\because ', '实数 %s, %s满足 ' % (symbs[0], symbs[1]) + eq.printing()])
        deduct_steps_str = ''
        for s in steps:
            deduct_steps_str += ' = ' + new_latex(s)
        deduct_steps_str = deduct_steps_str[2:]
        ded_ineq = OpValueWithNum.hold_ineq(steps[-1])
        self.steps.append(['\\therefore %s' % new_latex(deduct_steps_str), '>= %s' % new_latex(ded_ineq.sympify()[-1])])
        self.steps.append(['故%s' % new_latex(t_e), '最小值为:' + new_latex(ded_ineq.sympify()[-1])])
        self.output.append(BaseNumber(ded_ineq.sympify()[-1]))
        return self


# 5.多字母消元求最值
# paramer1:等式; paramer2 目标表达式; paramer3 题目text
class JunZhiBuDengshi005(BaseFunction):
    """
    已知a,b为正实数,a+2b=1,则\\frac{1}{a}+\\frac{1}{b}的最小值为().
    """
    def solver(self, *args):
        self.label.add('多字母消元求最值')
        self.steps.append([r'\because ' + args[0].printing(), ''])
        eq_left, eq_right = args[0].sympify()
        target_e = args[1].sympify()
        text_cond = args[2]
        symbs_interval_dict = {}
        if isinstance(text_cond, BaseIneqs):
            symbs_ineqs = args[2].value
            for s_ineq in symbs_ineqs:
                s_ineq_str = ''.join(map(str, s_ineq))
                symb = re.findall(r'[a-z]', s_ineq_str)[0]
                symbs_interval_dict[symb] = co.ineq_expr_list_2_interval(s_ineq)
        f = eq_left - eq_right
        symbs = list(f.free_symbols)
        symb1 = symbs[0]
        symb1_exprs = co.solve_symbol_value(BaseEq([f, 0]), symbol=symb1)
        symb1_expr = symb1_exprs[0]
        symb2 = symbs[1]
        symb2_exprs = co.solve_symbol_value(BaseEq([f, 0]), symbol=symb2)
        symb2_expr = symb2_exprs[0]
        if len(symb1_exprs) == 1 and (not fraction(cancel(symb1_expr))[1].is_real):  # x**2+6xy-1=0
            first_symbol, second_symbol = symb2, symb1
            second_symbol_coeff = fraction(cancel(symb1_expr))[1]
            second_symbol_subs = symb1_expr

        elif len(symb2_exprs) == 1 and (not fraction(cancel(symb2_expr))[1].is_real):
            self.steps.append([r'\therefore %s= 0' % new_latex(f), ''])
            first_symbol, second_symbol = symb1, symb2
            second_symbol_coeff = fraction(cancel(symb2_expr))[1]
            second_symbol_subs = symb2_expr

        else:
            # ---------------------------------------------------------------------------
            self.steps.append([r'\therefore %s= 0' % new_latex(f), ''])
            first_symbol, second_symbol = f.free_symbols
            second_symbol_terms, others = co.expr_symb_terms_and_others(f, symb=second_symbol)
            second_symbol_terms = collect(second_symbol_terms, second_symbol)
            right_hand = -others
            second_symbol_coeff = second_symbol_terms.subs(second_symbol, 1)
            second_symbol_subs = right_hand / second_symbol_coeff
        # ---------------------------------------------------------------------------
        self.steps.append(['因此: %s = %s' % (new_latex(second_symbol), new_latex(second_symbol_subs)), ''])
        new_target_e = simplify(expand(target_e.subs(second_symbol, second_symbol_subs)))
        if second_symbol_coeff.is_real:  # 如果分母是自然数, 则属于正常的一元二次方程求最值问题
            pow_coeff = new_target_e.coeff(pow(first_symbol, 2))  # 二次项系数
            target_e_factor, top_point = co.top_point_expr(new_target_e, x_symbol=first_symbol)  # 顶点式
            op_value = top_point[1]
            if pow_coeff > 0:
                self.steps.append(
                    [r'\because %s' % new_latex(target_e), ' = ' + new_latex(target_e_factor) + '>=' + new_latex(op_value)])
                self.steps.append([r'\because %s' % new_latex(target_e), ' 最小值为: ' + new_latex(op_value)])
            else:
                self.steps.append(
                    [r'\because %s' % new_latex(target_e), ' = ' + new_latex(target_e_factor) + '<=' + new_latex(op_value)])
                self.steps.append([r'\because %s' % new_latex(target_e), ' 最大值为: ' + new_latex(op_value)])
            if type(op_value) == NaN:
                raise Exception("Match Type Error")
            self.output.append(BaseValue(op_value))
            return self
        elif len(new_target_e.free_symbols) == 1:  # 分式不等式
            pass
        first_symb_interval = solveset(second_symbol_subs > 0, domain=S.Reals)
        cond_first_symb_interval = symbs_interval_dict.get(str(first_symbol), '')
        if cond_first_symb_interval:
            first_symb_interval = first_symb_interval.intersect(cond_first_symb_interval)
        target_frac_ineq = co.frac_asp_ineq(new_target_e, first_symb_interval,
                                            first_symbol)
        ineq_left, ineq_op, ineq_right = target_frac_ineq.sympify()
        self.steps.append(['\because %s' % target_e, ' = ' + new_latex(ineq_left) + ineq_op + new_latex(ineq_right)])
        op_result = '最小值' if ineq_op.find('>') >= 0 else '最大值'
        self.steps.append([op_result, ' 为 ' + str(ineq_right)])
        self.output.append(BaseValue(ineq_right))
        return self


# 6.指数与均值不等式结合求最值
class AveValueIneq003(BaseFunction):
    @staticmethod
    def BaseIneq_2_interval(base_ineq):
        base_ineq = co.to_base(base_ineq, BaseIneq)
        ineq_struct = base_ineq.sympify()
        ineq_struct = map(str, ineq_struct)
        s = filter(lambda t: not (t.find('>') >= 0 or t.find('<') >= 0) and not sympify(t).is_real, ineq_struct)[0]
        inl = co.ineq_expr_list_2_interval(ineq_struct)
        return s, inl

    def solver(self, *args):
        self.label.add('指数与均值不等式结合求最值')
        conds = args[0]  # 已知条件
        eq_print = args[1].printing()
        eq = co.eqsimp(args[1])
        eq_l, eq_r = eq.sympify()
        t_e = args[2].sympify()
        mons = map(lambda t: t[0] * t[1], t_e.as_coefficients_dict().items())
        sym_mons = filter(lambda mon: not mon.is_real, mons)
        if isinstance(conds, Reals):
            symbs = eq.free_symbols()
            symb_inls = map(lambda s: (s, Interval(-S.Infinity, S.Infinity)), symbs)
        if isinstance(conds, BaseIneqs):
            symb_inls = map(AveValueIneq003.BaseIneq_2_interval, conds.sympify())
        ineq = co.asp_ineq(t_e, symb_inls)
        self.steps.append(['由已知条件可知:%s > 0, %s  > 0' % (new_latex(sym_mons[0]), new_latex(sym_mons[1])), ''])
        self.steps.append(['因为 ' + eq_print, ''])
        l, op, r = ineq.sympify()
        self.steps.append(['所以 ' + ineq.printing(), ' = ' + new_latex(r.subs(str(eq_l), eq_r))])
        try:
            op_value = r.subs(eq_l, eq_r)
            self.output.append(BaseNumber(op_value))
        except Exception:
            pass
        return self


# 7.分式通过补项利用均值求最值
class JunzhiBudengshi007(BaseFunction):
    def solver(self, *args):
        self.label.add('分式通过补项利用均值求最值')
        first_arg = args[0]
        if isinstance(first_arg, BasePoly) or isinstance(first_arg, BaseExpression):
            target_e = first_arg.value
            if target_e.find('=') >= 0:
                target_info = target_e.split('=')
                first_arg = BaseEq(target_info)
            else:
                first_arg = BaseEq(['f(x)', target_e])
        elif isinstance(first_arg, BaseFunc):
            first_arg = BaseEq(
                [first_arg.value['name'] if first_arg.value['name'] else 'f(x)', first_arg.value['expression']])
        elif isinstance(first_arg, BaseFunceq):
            first_arg = BaseEq(first_arg.value)
        f_s, f = first_arg.sympify()  # 目标函数
        s_i = args[1]  # 变量的取值范围
        if isinstance(s_i, BaseCond):
            s_i = BaseIneq(s_i.inequality[0])
        mo, de = fraction(f)
        loged = False
        if isinstance(mo, log) and isinstance(de, log):
            loged = True
            mo_log_value = mo.args[0]
            de_log_value = de.args[0]
            f = mo_log_value if not mo_log_value.is_real else de_log_value
            d = fraction(cancel(f))[1]  # 分母
            self.steps.append(['令 g = %s' % new_latex(f), ''])
        else:
            d = fraction(cancel(f))[1]  # 分母
        if loged:
            symons, const = co.symoms_and_const(f)  # 变量多项式和常数
        else:
            symons, const = co.symoms_and_const(f)  # 变量多项式和常数
        fr_term = list(filter(lambda s: fraction(s)[1] != 1, symons))[0]  # 分式项
        u_mon = list(filter(lambda s: fraction(s)[1] == 1, symons))[0]
        fill_value = d - u_mon
        self.steps.append(['因为 ', s_i.printing()])
        try:
            fill_str = ('(%s) + %s + %s' % (d, str(fr_term), const - fill_value)).replace('+ -', '-')
            fill_f = sympify(fill_str, evaluate=False)
            new_ff = fill_f.subs(str(d), 't')
            new_ineq = co.asp_ineq(new_ff, (('t', Interval(0, S.Infinity, left_open=True)),))
        except Exception:
            # fill_f = co.frac_asp_ineq(f, co.ineq_expr_list_2_interval(s_i.value))
            # # new_ff = fill_f.subs(str(d), 't')
            # new_ineq = co.asp_ineq(new_ff, (('t', Interval(0, S.Infinity, left_open=True)),))
            new_ineq = co.frac_asp_ineq(f, co.ineq_expr_list_2_interval(s_i.value))
        self.steps.append(
            ['所以 ' + (args[0].printing() if not loged else new_latex(d)),
             ('= (%s) + %s + %s' % (new_latex(d), new_latex(fr_term), const - fill_value)).replace('+ -', '-')])
        r_latex = (r'2\sqrt {(%s)%s} + %s' % (new_latex(d), new_latex(fr_term), const - fill_value)).replace('+ -', '-')
        self.steps.append([' >= ' + r_latex, ' = %s' % new_ineq.sympify()[-1]])
        op_value = new_ineq.sympify()[-1]
        if loged:
            if not mo.is_real:  # 如果是分子
                op_value = mo.subs(d, op_value) / de
                self.steps.append(['所以 %s' % args[0].printing(), ' >= ' + new_latex(op_value)])
            else:
                op_value = mo / de.subs(d, op_value)
                self.steps.append(['所以 %s' % args[0].printing(), ' <= ' + new_latex(op_value)])
        self.output.append(BaseNumber(op_value))
        self.label.add('分式通过补项利用均值求最值')
        return self


# 8.分式型转化为基本不等式
class JunzhiBudengshi008(BaseFunction):
    def solver(self, *args):
        self.label.add('分式型转化为基本不等式')
        y_symbol, f = args[1].sympify()
        s_i = args[0]  # 变量的取值范围
        if isinstance(s_i, BaseCond):
            s_i = BaseIneq(s_i.inequality[0])
        x_symnol = list(f.free_symbols)[0]
        frac = cancel(f)
        m, de = fraction(frac)
        self.steps.append([r'\because ', s_i.printing()])
        if de.coeff(pow(x_symnol, 2)) != 0 and m.coeff(pow(x_symnol, 2)) == 0:  # 如果分母是二次多项式: 求最大值
            sim_f = co.quad_frac_simp(frac)
            mons, cond = co.symoms_and_const(sim_f)
            new_f = mons[0]
            new_f = 1 / new_f
            new_f_n, new_f_de = fraction(new_f)
            new_f_sim = co.psa_simp(new_f)
            new_f_sim = new_f_sim.subs(str(new_f_de), 't')
            ineq = co.asp_ineq(new_f_sim, (('t', Interval(0, S.Infinity, left_open=True)),))
            max_v = 1 / ineq.sympify()[-1]
            max_v = max_v + cond
            self.steps.append(['最大值为:', max_v])
            self.output.append(BaseValue(max_v))
            return self
        elif de.coeff(x_symnol) != 0:  # 如果分母是一次多项式: 求最小值
            sim_f = co.linear_frac_simp(frac)
            try:
                new_f = sim_f.subs(str(de), 't')
                ineq = co.asp_ineq(new_f, (('t', Interval(0, S.Infinity, left_open=True)),))
            except Exception:
                ineq = co.frac_asp_ineq(sim_f, co.ineq_expr_list_2_interval(s_i.value))
            self.steps.append([r'\therefore %s = ' % new_latex(f), ineq.printing()])
            op_value = ineq.sympify()[-1]
            self.steps.append(['最小值为:', new_latex(op_value)])
            self.output.append(BaseNumber(op_value))
        return self


# 形如 k*x + m/x**2 + d
class JunZhiBuDengShi013(BaseFunction):
    def solver(self, *args):
        cond_ineq = args[0]
        cond_intl = co.ineq_expr_list_2_interval(cond_ineq.value)
        cond_left = cond_intl.left
        _, target_f = args[1].sympify()
        target_f = sympify(str(target_f))
        x_symbol = list(target_f.free_symbols)[0]
        k = target_f.coeff(x_symbol)
        frac_coeff = target_f.coeff(str(1 / x_symbol ** 2))
        _, const = co.symoms_and_const(target_f)
        frac_mons = target_f - k * x_symbol - const
        self.steps.append([r'\because ', cond_ineq.printing()])
        k_half = k / 2
        k_half_mo = k_half * x_symbol
        op_value = 3 * pow(k_half * k_half * frac_mons, 1 / 3) + const
        if k > 0 and frac_coeff > 0 and cond_left >= 0:
            new_target_f = sympify('%s + %s + %s + %s' % (k_half_mo, k_half_mo, frac_mons, const), evaluate=False)
            self.steps.append(
                [r'\therefore y = %s' % new_latex(target_f), ' = %s' % new_latex(new_target_f) + ' >= ' + new_latex(op_value)])
        elif k < 0 and frac_coeff < 0 and cond_left >= 0:
            new_target_f = sympify('-%s - %s - %s + %s' % (k_half_mo, k_half_mo, frac_mons, const), evaluate=False)
            self.steps.append(
                [r'\therefore -y = %s' % new_latex(-target_f), ' = %s' % new_latex(new_target_f) + ' <= ' + new_latex(op_value)])
        else:
            raise 'fail'
        self.output.append(BaseNumber(op_value))
        label = sympify('k*x + m/x**2 + d')
        self.label.add(new_latex(label))
        return self


class FenShiBuDengShi(BaseFunction):
    @staticmethod
    def class013(quest_f):  # k*x + m/x**2
        quest_f = sympify(str(quest_f))
        x_symbol = list(quest_f.free_symbols)[0]
        k = quest_f.coeff(x_symbol)
        m = quest_f.coeff(str(1 / x_symbol ** 2))
        return k and m

    def solver(self, *args):
        self.label.add('解分式不等式')
        tmp_args = list(args)
        if isinstance(tmp_args[0], BaseBelong):
            v = tmp_args[0].value
            if '\u2208' in v and 'R^{+}' in v:
                tmp_args[0] = BaseIneq([v[0], '>', 0])
            elif '\u2208' in v and co.iterable(v[-1]):
                vi = v[-1]
                tmp_args[0] = BaseIneq(
                    [vi[1], ('<' if vi[0] == '(' else '<='), v[0], ('<' if vi[-1] == ')' else '<='), vi[2]])
        if isinstance(tmp_args[1], BasePoly):
            tmp_args[1] = BaseEq(['f', args[1].sympify()])
        _, f = tmp_args[1].sympify()
        if co.frac_simp(f) == f:  # 7.分式通过补项利用均值求最值
            r = JunzhiBudengshi007(verbose=True).solver(tmp_args[1], tmp_args[0])
            return r
        elif FenShiBuDengShi.class013(f):
            return JunZhiBuDengShi013(verbose=True).solver(*tmp_args)
        else:
            return JunzhiBudengshi008(verbose=True).solver(*tmp_args)

